<?php
/**
 * Created by PhpStorm.
 * User: inhere
 * Date: 2016/12/7
 * Time: 13:23
 */

namespace app\components;

use app\components\console\Input;
use app\components\console\Output;

/**
 * Class Command
 * @package app\components
 */
abstract class Command extends Controller
{
    protected $defaultAction = 'help';

    /**
     * @var Input
     */
    protected $in;

    /**
     * @var Output
     */
    protected $out;

    /**
     * allow display message tags in the command
     * @var array
     */
    protected $allowTags = ['description', 'usage', 'example'];

    public function __construct()
    {
        $this->in  = \App::get('input');
        $this->out = \App::get('output');
    }

    /**
     * @param $message
     * @param bool $nl
     */
    protected function write($message, $nl = true)
    {
        $this->out->write($message, $nl);
    }

    /**
     * show help of the class or specified action
     * @usage ./bin/app {class}/help [action]
     * @example ./bin/app home/help OR ./bin/app home/help index
     */
    final public function help()
    {
        if (!$data = \App::input()->get()) {
            $this->commands();
            return 0;
        }

        $keys = array_keys($data);
        $action = trim(array_shift($keys), '- ');
        $ref = new \ReflectionClass($this);
        $sName = lcfirst($ref->getShortName());

        if ( !$ref->hasMethod($action) || !$ref->getMethod($action)->isPublic() ) {
            $this->write("Command [<notice>$sName/$action</notice>] don't exist or don't allow access in the class.");
            return 0;
        }

        $m = $ref->getMethod($action);
        $tags = $this->parseDocCommentTags($m->getDocComment());

        foreach ($tags as $tag => $msg) {
            if (!$this->allowTags || in_array($tag, $this->allowTags)) {
                $this->write("<comment>$tag:</comment>\n   <info>$msg</info>");
            }
        }

        return 0;
    }

    /**
     * get command list of the class
     */
    final public function commands()
    {
        $ref = new \ReflectionClass($this);

        $name = $ref->getName();
        $this->write("This is in the command controller [<notice>$name</notice>]\n");

        if ( $desc = $this->parseDocCommentDetail($ref->getDocComment()) ) {
            $this->write("<comment>Description:</comment>\n  $desc");
        }

        $excludes = ['__construct','commands', 'distribute'];
        $sName = lcfirst($ref->getShortName());
        $this->write('<comment>Command list:</comment>');
        $this->write('  command   |   command description');

        foreach ($ref->getMethods() as $m) {
            if ($m->isPublic() && !in_array($m->getName(), $excludes)) {
                $desc = $this->parseDocCommentDetail($m->getDocComment());
                $this->write("  <info>$sName/" . $m->getName() . "</info>  $desc");
            }
        }

        $this->write("\n<comment>For more information please use </comment><info>$sName/help [action]</info>");
    }

    protected function getUsage($docComment)
    {
        $usage = '';
        preg_match('/@usage (.*)/',$docComment, $matched);

        if ( isset($matched[1]) ) {
            $usage = trim(trim($matched[1], '* '));
        }

        return $usage;
    }

    protected function getExample($docComment)
    {
        $exam = '';
        preg_match('/@example (.*)/',$docComment, $matched);

        if ( isset($matched[1]) ) {
            $exam = trim(trim($matched[1], '* '));
        }

        return $exam;
    }

    protected function getDescString($docComment)
    {
        preg_match('/\* [\w\s-]{3,}/',$docComment, $matched);
        $desc = '';
        if ( isset($matched[0]) ) {
            $desc = trim(trim($matched[0], '* '));
            $desc = ucfirst($desc);
        }

        return $desc;
    }

    /*
     * 以下三个方法来自 yii2 console/Controller.php
     */

    /**
     * Parses the comment block into tags.
     * @param string $comment the comment block
     * @return array the parsed tags
     */
    protected function parseDocCommentTags($comment)
    {
//        $comment = $reflection->getDocComment();
        $comment = "@description \n" . strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/'))), "\r", '');
        $parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY);
        $tags = [];
        foreach ($parts as $part) {
            if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) {
                $name = $matches[1];
                if (!isset($tags[$name])) {
                    $tags[$name] = trim($matches[2]);
                } elseif (is_array($tags[$name])) {
                    $tags[$name][] = trim($matches[2]);
                } else {
                    $tags[$name] = [$tags[$name], trim($matches[2])];
                }
            }
        }
        return $tags;
    }

    /**
     * Returns the first line of docblock.
     *
     * @param  $comment
     * @return string
     */
    protected function parseDocCommentSummary($comment)
    {
        $docLines = preg_split('~\R~u', $comment);
        if (isset($docLines[1])) {
            return trim($docLines[1], "\t *");
        }
        return '';
    }

    /**
     * Returns full description from the docblock.
     *
     * @param  $comment
     * @return string
     */
    protected function parseDocCommentDetail($comment)
    {
        $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/'))), "\r", '');
        if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) {
            $comment = trim(substr($comment, 0, $matches[0][1]));
        }
//        if ($comment !== '') {
//            return $this->write($comment);
//        }

        return $comment;
    }
}